package com.xingchi.tornado.core.utils;

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class DigestUtils {

    private static final int BUFFER_SIZE = 4096;

    // 算法常量
    public static final String MD5_ALGORITHM = "MD5";
    public static final String SHA1_ALGORITHM = "SHA-1";
    public static final String SHA256_ALGORITHM = "SHA-256";

    /**
     * 计算MD5摘要值
     *
     * @param input 字符串
     * @return 摘要值
     */
    public static String md5(String input) {
        return digest(input, MD5_ALGORITHM);
    }

    /**
     * 计算MD5摘要值
     *
     * @param input 字节数组
     * @return 摘要值
     */
    public static String md5(byte[] input) {
        return digest(input, MD5_ALGORITHM);
    }

    /**
     * 计算MD5摘要值
     *
     * @param file 文件
     * @return 摘要值
     */
    public static String md5(File file) {
        return fileDigest(file, MD5_ALGORITHM);
    }

    /**
     * 计算MD5摘要值
     *
     * @param inputStream 输入流
     * @return 摘要值
     */
    public static String md5(InputStream inputStream) {
        return inputStreamDigest(inputStream, MD5_ALGORITHM);
    }

    /**
     * 计算SHA-1摘要值
     *
     * @param input 字符串
     * @return 摘要值
     */
    public static String sha1(String input) {
        return digest(input, SHA1_ALGORITHM);
    }

    /**
     * 计算SHA-1摘要值
     *
     * @param input 字节数组
     * @return 摘要值
     */
    public static String sha1(byte[] input) {
        return digest(input, SHA1_ALGORITHM);
    }

    /**
     * 计算SHA-1摘要值
     *
     * @param file 文件
     * @return 摘要值
     */
    public static String sha1(File file) {
        return fileDigest(file, SHA1_ALGORITHM);
    }

    /**
     * 计算SHA-1摘要值
     *
     * @param inputStream 输入流
     * @return 摘要值
     */
    public static String sha1(InputStream inputStream) {
        return inputStreamDigest(inputStream, SHA1_ALGORITHM);
    }

    /**
     * 计算SHA-256摘要值
     *
     * @param input 字符串
     * @return 摘要值
     */
    public static String sha256(String input) {
        return digest(input, SHA256_ALGORITHM);
    }

    /**
     * 计算SHA-256摘要值
     *
     * @param input 字节数组
     * @return 摘要值
     */
    public static String sha256(byte[] input) {
        return digest(input, SHA256_ALGORITHM);
    }

    /**
     * 计算SHA-256摘要值
     *
     * @param file 文件
     * @return 摘要值
     */
    public static String sha256(File file) {
        return fileDigest(file, SHA256_ALGORITHM);
    }

    /**
     * 计算SHA-256摘要值
     *
     * @param inputStream 输入流
     * @return 摘要值
     */
    public static String sha256(InputStream inputStream) {
        return inputStreamDigest(inputStream, SHA256_ALGORITHM);
    }

    /**
     * 计算SM3摘要值
     *
     * @param input 字符串
     * @return 摘要值
     */
    private static String sm3(String input) {
        byte[] data = input.getBytes();
        return sm3(data);
    }

    /**
     * 计算SM3摘要值
     *
     * @param input 字节数组
     * @return 摘要值
     */
    private static String sm3(byte[] input) {
        SM3Digest digest = new SM3Digest();
        digest.update(input, 0, input.length);
        byte[] result = new byte[digest.getDigestSize()];
        digest.doFinal(result, 0);
        return Hex.toHexString(result);
    }

    /**
     * 计算SM3摘要值
     *
     * @param file 文件
     * @return 摘要值
     */
    private static String sm3(File file) {
        try {
            try (InputStream inputStream = Files.newInputStream(file.toPath())) {
                return sm3(inputStream);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算SM3摘要值
     *
     * @param inputStream 输入流
     * @return 摘要值
     */
    private static String sm3(InputStream inputStream) {
        try {
            SM3Digest digest = new SM3Digest();
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }
            byte[] result = new byte[digest.getDigestSize()];
            digest.doFinal(result, 0);
            return Hex.toHexString(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算字符串的摘要值
     *
     * @param input     字符串值
     * @param algorithm 算法名
     * @return 摘要值
     */
    private static String digest(String input, String algorithm) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            byte[] bytes = digest.digest(input.getBytes());
            return Hex.toHexString(bytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算字节数组的摘要值
     *
     * @param input     字节数组
     * @param algorithm 摘要算法
     * @return 摘要值
     */
    private static String digest(byte[] input, String algorithm) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            byte[] bytes = digest.digest(input);
            return Hex.toHexString(bytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算文件摘要值的通用方法
     *
     * @param file      文件生成摘要
     * @param algorithm 摘要算法
     * @return 摘要值
     */
    private static String fileDigest(File file, String algorithm) {
        try {
            try (InputStream inputStream = Files.newInputStream(file.toPath())) {
                return inputStreamDigest(inputStream, algorithm);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算输入流的摘要值
     *
     * @param inputStream 输入流
     * @param algorithm   摘要算法
     * @return 摘要值
     */
    private static String inputStreamDigest(InputStream inputStream, String algorithm) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }
            byte[] bytes = digest.digest();
            return Hex.toHexString(bytes);
        } catch (IOException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

}
